/**
 * 节流原理：在一定时间内，只能触发一次
 *节流的意思是，规定时间内，只触发一次。比如我们设定500ms，
  在这个时间内，无论点击按钮多少次，它都只会触发一次。具体场景可以是抢购时候，由于有无数人 快速点击按钮，
  如果每次点击都发送请求，就会给服务器造成巨大的压力，但是我们进行节流后，就会大大减少请求的次数。
 * @param {Function} func 要执行的回调函数
 * @param {Number} wait 延时的时间
 * @param {Boolean} immediate 是否立即执行
 * @return null
 * throttle：setTimeout(fn,x - elapsed)。节流的实现要多两个额外的变量，
 * 一个是记录上一次函数fn执行的时间(lastExec)，
 * 一个是记录流逝的时间(elapsed) = 当前setTimeout创建的时间 - 上一次函数fn执行的时间(lastExec)，
 * 每产生一个新的事件（创建新的setTimeout）,都会先去取消上一个setTimeout，
 * 所以它的定时器要这么写setTimeout(fn,x - elapsed)就能每x毫秒执行一次函数fn了，
 * 每一次函数fn被执行都会更新lastExec的值为当前时间。
 */

export  function throttle(callback,delay = 500, options) {
	const {
		noTrailing = false,
		noLeading = true,
		debounceMode = undefined
	} = options || {};
	/*
	 * After wrapper has stopped being called, this timeout ensures that
	 * `callback` is executed at the proper times in `throttle` and `end`
	 * debounce modes.
	 */
	let timeoutID;
	let cancelled = false;

	// Keep track of the last time `callback` was executed.
	let lastExec = 0;

	// Function to clear existing timeout
	function clearExistingTimeout() {
		if (timeoutID) {
			clearTimeout(timeoutID);
		}
	}

	// Function to cancel next exec
	function cancel(options) {
		const { upcomingOnly = false } = options || {};
		clearExistingTimeout();
		cancelled = !upcomingOnly;
	}

	/*
	 * The `wrapper` function encapsulates all of the throttling / debouncing
	 * functionality and when executed will limit the rate at which `callback`
	 * is executed.
	 */
	function wrapper(...arguments_) {
		let self = this;
		let elapsed = Date.now() - lastExec;

		if (cancelled) {
			return;
		}

		// Execute `callback` and update the `lastExec` timestamp.
		function exec() {
			lastExec = Date.now();
			callback.apply(self, arguments_);
		}

		/*
		 * If `debounceMode` is true (at begin) this is used to clear the flag
		 * to allow future `callback` executions.
		 */
		function clear() {
			timeoutID = undefined;
		}

		if (!noLeading && debounceMode && !timeoutID) {
			/*
			 * Since `wrapper` is being called for the first time and
			 * `debounceMode` is true (at begin), execute `callback`
			 * and noLeading != true.
			 */
			exec();
		}

		clearExistingTimeout();

		if (debounceMode === undefined && elapsed > delay) {
			if (noLeading) {
				/*
				 * In throttle mode with noLeading, if `delay` time has
				 * been exceeded, update `lastExec` and schedule `callback`
				 * to execute after `delay` ms.
				 */
				lastExec = Date.now();
				if (!noTrailing) {
					timeoutID = setTimeout(debounceMode ? clear : exec, delay);
				}
			} else {
				/*
				 * In throttle mode without noLeading, if `delay` time has been exceeded, execute
				 * `callback`.
				 */
				exec();
			}
		} else if (noTrailing !== true) {
			/*
			 * In trailing throttle mode, since `delay` time has not been
			 * exceeded, schedule `callback` to execute `delay` ms after most
			 * recent execution.
			 *
			 * If `debounceMode` is true (at begin), schedule `clear` to execute
			 * after `delay` ms.
			 *
			 * If `debounceMode` is false (at end), schedule `callback` to
			 * execute after `delay` ms.
			 */
			timeoutID = setTimeout(
				debounceMode ? clear : exec,
				debounceMode === undefined ? delay - elapsed : delay
			);
		}
	}

	wrapper.cancel = cancel;

	// Return the wrapper function.
	return wrapper;
}

/**
 * 防抖的意思是，在连续的操作中，无论进行了多长时间，只有某一次的操作后在指定的时间内没有再操作，
 * 这一次才被判定有效。具体场景可以搜索框输入关键字过程中实时 请求服务器匹配搜索结果，如果不进行处理，
 * 那么就是输入框内容一直变化，导致一直发送请求。
 * 如果进行防抖处理，结果就是当我们输入内容完成后，一定时间(比如500ms)没有再 输入内容，这时再触发请求。
 * 
 * 
 * debounce：创建一个指定x毫秒的setTimeout(fn,x)，
 * 如果在该setTimeout(fn,x)的x毫秒内再次产生新的事件，就会先去取消该setTimeout，
 * 然后重新创建一个指定x毫秒的setTimeout(fn,x)，不断重复这一过程，直到没有再产生新的事件，
 * x毫秒后调用函数fn。
 * @param {*} callback 
 * @param {*} delay 
 * @param {*} options 
 * @returns 
 */
export function debounce(callback,delay= 500, options) {
	const { atBegin = false } = options || {};
	return throttle(callback, delay, { debounceMode: atBegin !== false });
}